﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using ValidationEngine.Commons.Enums;
using ValidationEngine.Tools;
using ValidationEngine.Commons;
using DevExpress.XtraLayout;
using ValidationEngine.Commons.Interfaces;

namespace ValidationEngine.Core
{
    /// <summary>
    /// IViewMgr implementation
    /// </summary>
    internal class ViewMgr : IViewMgr
    {
        #region Constants
        private const string p_DataSource = "DataSource";
        #endregion

        #region Members
        private Hashtable _controls = new Hashtable();
        private HashSet<BindingSource> _uniqueSources = new HashSet<BindingSource>();
        private Dictionary<Type, Dictionary<string, List<Control>>> _controlsSortedByModel = new Dictionary<Type, Dictionary<string, List<Control>>>();

        private readonly IXErrorProvider _errProvider = new XErrorProvider();
        #endregion

        #region Ctors
        public ViewMgr()
        { }
        #endregion

        #region Tools
        /// <summary>
        /// Scans view for controls and registers control-binding_source relations
        /// </summary>
        /// <param name="view">View</param>
        /// <param name="parentType">View root model's parent type</param>
        private void ScanControls(IBindableView view, Type parentType)
        {
            var modelType = GetRootModelTypeForView(view);
            var controls = GetControlsFromView(view);

            try
            {
                foreach (Control ctrl in controls)
                {
                    IBindableView bindableCtrl = ctrl as IBindableView;
                    PropertyInfo dsProperty = ctrl.GetType().GetProperty(p_DataSource);

                    if (null != bindableCtrl)
                    {
                        AddControl(ctrl, bindableCtrl.Source, bindableCtrl.ParentSourceMember);
                        AddControlToSortedItems(ctrl, bindableCtrl.ParentSourceMember, modelType, parentType);
                        // Try scan IBindableView for inner controls
                        ScanControls(bindableCtrl, modelType);
                    }
                    else if (ctrl.DataBindings.Count > 0)
                    {
                        foreach (Binding binding in ctrl.DataBindings)
                        {
                            if (AddControlByBinding(binding, modelType, parentType))
                            {
                                // stop analyse bindings after one is added successfuly
                                break;
                            }
                        }
                    }
                    else if (null != dsProperty)
                    {
                        AddControlByDataSource(ctrl, dsProperty, modelType, parentType);
                    }
                }
            }
            catch (Exception)
            {
                throw new EngineCoreExcception("Problem scanning controls.");
            }
        }

        /// <summary>
        /// Returns view's root model type and prepares controls sorted by model collection
        /// </summary>
        /// <param name="view">View</param>
        /// <returns>Model's type</returns>
        private Type GetRootModelTypeForView(IBindableView view)
        {
            Type modelType = null;
            if (!view.ParentSourceMember.IsNullOrEmpty())
            {
                var propInfo = GetModelTypeFromSource(view.Source).GetProperty(view.ParentSourceMember);
                if (null != propInfo)
                {
                    if (propInfo.PropertyType.IsGenericType && propInfo.PropertyType.GetGenericTypeDefinition() == typeof(BindingList<>))
                    {
                        modelType = propInfo.PropertyType.GetGenericArguments()[0];
                    }
                    else
                    {
                        modelType = propInfo.PropertyType;
                    }
                }
            }
            else
            {
                modelType = GetModelTypeFromSource(view.Source);
            }
            if (null != modelType)
            {
                if (!_controlsSortedByModel.ContainsKey(modelType))
                {
                    _controlsSortedByModel.Add(modelType, new Dictionary<string, List<Control>>());
                }
            }
            else
            {
                throw new EngineCoreExcception("ModelType can not be determined.");
            }
            return modelType;
        }

        /// <summary>
        /// Return binding source DataSource underlying type
        /// </summary>
        /// <param name="source">Binding source</param>
        /// <returns>underlying type</returns>
        private Type GetModelTypeFromSource(BindingSource source)
        {
            BindingSource bs = source.DataSource as BindingSource;
            if (null != bs)
            {
                if (!bs.DataMember.IsNullOrEmpty())
                {
                    var temp = GetModelTypeFromSource(bs);
                    var pi = temp.GetProperty(bs.DataMember);
                    if (null != pi)
                    {
                        if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof(BindingList<>))
                        {
                            return pi.PropertyType.GetGenericArguments()[0];
                        }
                        else
                        {
                            return pi.PropertyType;
                        }
                    }
                }
                return GetModelTypeFromSource(bs);
            }
            if (source.DataSource is Type)
            {
                return (Type)source.DataSource;
            }
            return source.DataSource.GetType();
        }

        /// <summary>
        /// Returns view controls
        /// </summary>
        /// <param name="view">View (instance of Form or Control)</param>
        /// <returns>Controls collection</returns>
        private static IEnumerable<Control> GetControlsFromView(IBindableView view)
        {
            IEnumerable<Control> temp = null;
            var form = view as Form;
            if (form != null)
            {
                temp = form.Controls.Cast<Control>();
            }
            var control = view as Control;
            if (null != control)
            {
                temp = control.Controls.Cast<Control>();
            }
            return GetAllControls(temp);
        }

        private static IEnumerable<Control> GetAllControls(IEnumerable<Control> temp)
        {
            List<Control> controls = new List<Control>();
            if (null != temp)
            {
                foreach (var ctrl in temp)
                {
                    if (ctrl is LayoutControl || ctrl is DevExpress.XtraEditors.GroupControl)
                    {
                        controls.AddRange(GetAllControls(ctrl.Controls.Cast<Control>()));
                    }
                    else
                    {
                        controls.Add(ctrl);
                    }
                }
                
            }
            return controls;
        }

        /// <summary>
        /// Registers control by DataSource property value
        /// </summary>
        /// <param name="control">Control to add</param>
        /// <param name="dsProperty">DataSource property info</param>
        /// <param name="modelType">Model's type</param>
        /// <param name="parentType">Model's parent type</param>
        private void AddControlByDataSource(Control control, PropertyInfo dsProperty, Type modelType, Type parentType)
        {
            BindingSource source = dsProperty.GetValue(control, null) as BindingSource;
            if (null != source)
            {
                AddControl(control, source, source.DataMember);
                AddControlToSortedItems(control, source.DataMember, modelType, parentType);
            }
        }

        /// <summary>
        /// Register control from binding
        /// </summary>
        /// <param name="binding">Binding item</param>
        /// <param name="modelType">Model type</param>
        /// <param name="parentType">Model's parent type</param>
        /// <returns></returns>
        private bool AddControlByBinding(Binding binding, Type modelType, Type parentType)
        {
            #region Agruments validation
            if (null == binding)
            {
                throw new ArgumentNullException("binding");
            }
            if (null == binding.Control)
            {
                throw new ArgumentNullException("binding.Control");
            }
            #endregion

            BindingSource source = binding.DataSource as BindingSource;
            if (null != source)
            {
                AddControl(binding.Control, source, binding.BindingMemberInfo.BindingField);
                AddControlToSortedItems(binding.Control, binding.BindingMemberInfo.BindingField, modelType, parentType);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Adds control to sorted by type control's list
        /// </summary>
        /// <param name="control">Control to add</param>
        /// <param name="propertyName">Model's property name</param>
        /// <param name="modelType">Model's type</param>
        /// <param name="parentType">Model's parent type</param>
        private void AddControlToSortedItems(Control control, string propertyName, Type modelType, Type parentType)
        {
            if (propertyName.IsNullOrEmpty())
            {
                return;
            }
            // resolve property's model type
            if (null == modelType.GetProperty(propertyName))
            {
                if (null == parentType || null == parentType.GetProperty(propertyName))
                {
                    throw new EngineCoreExcception("Unresolved type.");
                }
                modelType = parentType;
            }
            if (!_controlsSortedByModel[modelType].ContainsKey(propertyName))
            {
                _controlsSortedByModel[modelType].Add(propertyName, new List<Control>());
            }
            _controlsSortedByModel[modelType][propertyName].Add(control);
        }

        /// <summary>
        /// Registers control and related binding source
        /// </summary>
        /// <param name="ctrl">Control to add</param>
        /// <param name="source">Control's related binding source</param>
        /// <param name="propertyName">Model's property name</param>
        private void AddControl(Control ctrl, BindingSource source, string propertyName)
        {
            _controls.Add(ctrl, source);
            _uniqueSources.Add(source);
        }
        #endregion

        #region IViewMgr
        /// <summary>
        /// Returns controls related to model's property
        /// </summary>
        /// <param name="modelType">Model type</param>
        /// <param name="propertyName">Property name</param>
        /// <returns></returns>
        public IEnumerable<Control> GetControlsByModelProperty(Type modelType, string propertyName)
        {
            if (_controlsSortedByModel.ContainsKey(modelType) && _controlsSortedByModel[modelType].ContainsKey(propertyName))
            {
                return _controlsSortedByModel[modelType][propertyName];
            }
            return new List<Control>();
        }

        /// <summary>
        /// Returns control related binding source
        /// </summary>
        /// <param name="ctrl">Control</param>
        /// <returns>Related binding source</returns>
        public BindingSource GetSourceByControl(Control ctrl)
        {
            var bs = _controls[ctrl];
            return bs == null ? null : (BindingSource)bs;
        }

        /// <summary>
        /// Resets all controls binding sources
        /// </summary>
        public void ResetAllBindings()
        {
            _uniqueSources.ToList().ForEach(s => s.ResetBindings(false));
        }

        public void SetValidationInfoForControl(Control ctrl, IValidationInfo info)
        {
            if (!info.IsValid)
            {
                _errProvider.SetError(ctrl, info.ErrorText);
            }
            else if (info.HasWarnings)
            {
                _errProvider.SetWarning(ctrl, info.WarningText);
            }
            else if (info.HasInfos)
            {
                _errProvider.SetInfo(ctrl, info.InfoText);
            }
            else
            {
                _errProvider.Clear(ctrl);
            }
        }

        public void ScanControls(IBindableView view)
        {
            this.ScanControls(view, null);
        }
        #endregion
    }
}
